# Dockerfile
# 基于 avalonia 框架的开发环境搭建

#################################################
# 基础镜像 .NET 镜像
#################################################
# 构建阶段
# 使用 .NET SDK 作为基础镜像，用于开发环境（这种写法本地有则用本地，无则自动 podman pull 或 docker pull）
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build


#################################################
# avalonia 模板版本
#################################################
ARG AVALONIA_TEMPLATE_VERSION=11.2.1



#################################################
# 目录结构定义 - 所有路径都在这里集中定义和管理
#################################################
# 基础用户配置 - 定义容器内开发用户的基本信息
ARG USERNAME=vscode                                # 开发用户名称，默认使用vscode
ARG USER_UID=1000                                 # 用户ID，使用1000避免权限问题
ARG USER_GID=$USER_UID                            # 用户组ID，与用户ID保持一致

## 用户相关目录 - 存放用户特定的配置和文件
ARG USER_HOME=/home/$USERNAME                      # 用户主目录，存放用户个人文件
ARG USER_DOTNET=$USER_HOME/.dotnet                # .NET相关配置目录，包含SDK和工具
ARG USER_DOTNET_AVALONIA_TEMPLATES=$USER_DOTNET/templates  # Avalonia模板目录，存放项目模板

## 开发环境目录 - 用于开发和构建过程
ARG DEV_WORKSPACE=/workspaces                      # 开发工作空间根目录，用于存放项目文件
ARG DEV_PROJECT=$DEV_WORKSPACE/project             # 具体项目目录，实际开发工作目录
ARG DEV_BUILD=/build                               # 构建临时目录，用于存放构建过程文件

## 包管理目录 - 用于存放各种包管理器的缓存和配置
ARG PKG_NUGET_HOME=$USER_HOME/.nuget              # NuGet根目录，存放NuGet相关内容
ARG PKG_NUGET_CONFIG=$PKG_NUGET_HOME/NuGet        # NuGet配置目录，存放配置文件
ARG PKG_NUGET_CACHE=$PKG_NUGET_HOME/packages      # NuGet包缓存目录，存放下载的包
ARG PKG_APT_CACHE=/var/cache/apt                  # APT缓存目录，存放下载的包
ARG PKG_APT_LISTS=$PKG_APT_CACHE/lists           # APT包索引缓存目录，存放包信息

## 系统配置目录 - 存放系统级配置文件
ARG SYS_ETC=/etc                                   # 系统配置根目录，标准位置
ARG SYS_APT_CONF=$SYS_ETC/apt/apt.conf.d          # APT配置目录，存放APT行为配置
ARG SYS_APT_SOURCES=$SYS_ETC/apt/sources.list     # APT源配置文件，定义软件源
ARG SYS_APT_SOURCES_D=$SYS_ETC/apt/sources.list.d # APT源扩展配置目录，存放额外源
ARG SYS_SUDO_CONF=$SYS_ETC/sudoers.d              # sudo配置目录，存放sudo权限规则

# 设置Android SDK的版本和路径
ARG ANDROID_SDK_VERSION="commandlinetools-linux-8512546_latest" # Android SDK的版本号
ARG ANDROID_HOME=/opt/android-sdk # Android SDK的安装目录
ARG ANDROID_CACHE=$USER_HOME/.android # Android SDK的缓存目录

#################################################
# 创建开发用户 - 设置开发环境的用户权限
#################################################
RUN groupadd --gid $USER_GID $USERNAME \
    # 创建用户并配置shell
    && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \
    # 创建sudo配置目录
    && mkdir -p $SYS_SUDO_CONF \
    # 配置用户的sudo权限，允许无密码执行sudo
    && echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" > $SYS_SUDO_CONF/$USERNAME \
    # 设置sudo配置文件的权限
    && chmod 0440 $SYS_SUDO_CONF/$USERNAME


#################################################
# 设置环境变量 - 配置开发环境的行为
#################################################
    # 禁用 .NET CLI 的遥测数据收集
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 \
    # 跳过 .NET 的首次运行体验，加快环境准备
    DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 \
    # 不生成 NuGet 包的 XML 文档，减少磁盘占用
    NUGET_XMLDOC_MODE=skip \
    # 指定 NuGet 包的缓存目录，确保包的复用
    NUGET_PACKAGES=$PKG_NUGET_CACHE \
    # 设置 apt-get 在安装过程中不需要交互
    DEBIAN_FRONTEND=noninteractive \
    # 设置 Avalonia 的屏幕缩放因子，确保正确显示
    AVALONIA_SCREEN_SCALE_FACTORS=1 \
    # 标识当前在容器环境中运行
    DOTNET_RUNNING_IN_CONTAINER=true \
    # 设置 ANDROID_HOME 目录，用于存储 Android SDK 的下载文件
    ANDROID_HOME=$ANDROID_HOME \
    # 设置 ANDROID_CACHE 目录，用于缓存 Android SDK 的下载文件
    ANDROID_CACHE=$ANDROID_CACHE \
    # 设置 PATH 环境变量，包括 tools、tools/bin 和 platform-tools
    PATH=$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin:$ANDROID_HOME/platform-tools:$PATH 


#################################################
# 配置工作目录 - 设置构建过程的工作目录
#################################################
WORKDIR $DEV_BUILD


#################################################
# Android SDK 
#################################################
# 创建 android 必要目录
RUN mkdir -p $ANDROID_HOME $ANDROID_CACHE $USER_HOME/.android \
    && chown -R $USERNAME:$USERNAME $ANDROID_HOME $ANDROID_CACHE $USER_HOME/.android

# uzip,file,openjdk,apt，都使用这个源加速下载
RUN if [ -f $SYS_APT_SOURCES ]; then \
    # 替换默认源为华为云镜像
    sed -i 's/deb.debian.org/mirrors.huaweicloud.com/g' $SYS_APT_SOURCES; \
    elif [ -f $SYS_APT_SOURCES_D/debian.sources ]; then \
        # 对于新版本debian的源文件进行替换
        sed -i 's/deb.debian.org/mirrors.huaweicloud.com/g' $SYS_APT_SOURCES_D/debian.sources; \
    else \
        # 如果找不到源文件，报错退出
        echo "❌ 未找到可修改的源文件" && exit 1; \
    fi \
    && echo "✅ 已将软件源替换为华为云镜像" \
    && apt-get clean \
    && apt-get update \
    && echo "📦 软件源更新完成" \
    && echo "开始验证软件源可用性..." \
    && apt-get install -y --no-install-recommends wget \
    && wget -q --spider https://mirrors.huaweicloud.com/debian/dists/stable/Release \
    && if [ $? -eq 0 ]; then \
        echo "华为云镜像源可用！"; \
    else \
        echo "华为云镜像源不可用，将回退到默认源"; \
        sed -i 's/mirrors.huaweicloud.com/deb.debian.org/g' $SYS_APT_SOURCES; \
    fi \
    && apt-get purge -y wget \
    && apt-get clean

# 安装 unzip 和 file 工具（下一步要使用）
RUN set -e \
    && sources_files=$(find /etc/apt -type f \( -name "sources.list" -o -name "*.list" \)) \
    && for file in $sources_files; do \
        if [ -f "$file" ]; then \
            sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' "$file"; \
            sed -i 's/security.debian.org/mirrors.ustc.edu.cn/g' "$file"; \
        fi \
    done \
    && apt-get clean \
    && apt-get update

# 安装 unzip 和 file（下一步要使用）
RUN apt-get install -y --no-install-recommends unzip file \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# 安装 Java（openjdk@17）
RUN mkdir -p /etc/profile.d \
    && apt-get update \
    && apt-get install -y --no-install-recommends openjdk-17-jdk \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* \
    && JAVA_PATH=$(which java) \
    && if [ -z "$JAVA_PATH" ]; then \
        echo "错误：未找到 Java 可执行文件" >&2; \
        exit 1; \
    fi \
    && JAVA_PATH=$(readlink -f "$JAVA_PATH") \
    && JAVA_HOME=$(dirname $(dirname "$JAVA_PATH")) \
    && if [ ! -d "$JAVA_HOME" ]; then \
        echo "错误：无效的 JAVA_HOME 目录" >&2; \
        exit 1; \
    fi \
    && echo "JAVA_HOME 设置为: $JAVA_HOME" \
    && echo "export JAVA_HOME=$JAVA_HOME" >> /etc/profile.d/java.sh \
    && echo "export PATH=\$JAVA_HOME/bin:\$PATH" >> /etc/profile.d/java.sh \
    && chmod +x /etc/profile.d/java.sh \
    && echo "Java 环境配置完成"

# 下载并安装 Android SDK
RUN set -e \
    && SDK_DOWNLOAD_URL1="https://dl.google.com/android/repository/commandlinetools-linux-10406996_latest.zip" \
    && SDK_DOWNLOAD_URL2="https://mirrors.tuna.tsinghua.edu.cn/android/repository/commandlinetools-linux-10406996_latest.zip" \
    && SDK_DOWNLOAD_SUCCESS=false \
    && for url in "$SDK_DOWNLOAD_URL1" "$SDK_DOWNLOAD_URL2"; do \
        echo "尝试下载 Android SDK 从 $url"; \
        if curl -sL --max-time 300 "$url" -o android-sdk.zip; then \
            echo "下载完成，检查文件..."; \
            ls -l android-sdk.zip; \
            file android-sdk.zip; \
            if unzip -qt android-sdk.zip; then \
                rm -rf $ANDROID_HOME/cmdline-tools \
                && mkdir -p $ANDROID_HOME/cmdline-tools/latest \
                && unzip -q android-sdk.zip -d $ANDROID_HOME/cmdline-tools/latest \
                && mv $ANDROID_HOME/cmdline-tools/latest/cmdline-tools/* $ANDROID_HOME/cmdline-tools/latest/ \
                && rmdir $ANDROID_HOME/cmdline-tools/latest/cmdline-tools \
                && SDK_DOWNLOAD_SUCCESS=true; \
                break; \
            else \
                echo "ZIP文件验证失败：$url"; \
                rm -f android-sdk.zip; \
            fi \
        else \
            echo "下载失败：$url"; \
        fi \
    done \
    && if [ "$SDK_DOWNLOAD_SUCCESS" = "false" ]; then \
        echo "所有 Android SDK 下载源都失败了"; \
        exit 1; \
    fi \
    && rm -f android-sdk.zip \
    && if [ ! -f "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" ]; then \
        echo "未找到 sdkmanager，下载可能不完整"; \
        exit 1; \
    fi \
    # 直接安装指定版本，跳过更新和同步
    && echo "开始安装 Android SDK 组件..." \
    && yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager \
        --sdk_root=$ANDROID_HOME \
        --no_https \
        --install \
        "platform-tools;35.0.2" \
        "build-tools;35.0.0" \
        "platforms;android-34" \
        "system-images;android-35;google_apis;x86_64" \
    && mkdir -p $ANDROID_CACHE && chown -R $USERNAME:$USERNAME $ANDROID_CACHE \
    && rm -rf $ANDROID_HOME/.android/*

#################################################
# APT 管理
#################################################

# 配置 APT 缓存和持久化 - 优化包管理性能
RUN echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > $SYS_APT_CONF/keep-cache \
    # 禁止自动安装推荐的软件包，减少不必要的安装
    && echo 'APT::Install-Recommends "false";' >> $SYS_APT_CONF/keep-cache

# 使用缓存安装依赖 - 安装开发环境所需的包
# 使用缓存挂载机制提高构建效率
RUN --mount=type=cache,target=$PKG_APT_LISTS \
    # 更新包索引，如果失败则使用缓存
    apt-get update || echo "APT update failed, using cached packages" \
    # 安装必要的开发工具和库
    && apt-get install -y \
        # 基础开发工具
        git wget curl sudo ca-certificates \
        # UI开发支持
        fontconfig libgdiplus nano \
        # X11图形界面支持
        libx11-dev libxrandr-dev libxinerama-dev \
        # 输入设备支持
        libxcursor-dev libxi-dev \
        # 远程开发支持
        openssh-client \
    # 清理APT缓存，减小镜像大小
    && apt-get clean && rm -rf $PKG_APT_LISTS/*


#################################################
# NuGet 管理 - 挂载缓存、创建目录、设置目录权限、配置 NuGet 源
#################################################
# NuGet 配置和缓存 - 设置包管理器
# 使用缓存挂载来优化包的存储和访问
RUN --mount=type=cache,target=$PKG_NUGET_CACHE,sharing=locked,mode=777 \
    # 创建必要的目录
    mkdir -p $PKG_NUGET_CACHE $PKG_NUGET_CONFIG \
    # 设置目录权限
    && chown -R $USERNAME:$USERNAME $PKG_NUGET_CACHE $PKG_NUGET_CONFIG \
    # 配置 NuGet 源
    && echo '<?xml version="1.0" encoding="utf-8"?>\
<configuration>\
  <packageSources>\
    <add key="azure" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" protocolVersion="3" />\
    <add key="huawei" value="https://mirrors.huaweicloud.com/repository/nuget/v3/index.json" protocolVersion="3" />\
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />\
  </packageSources>\
  <config>\
    <add key="globalPackagesFolder" value="$PKG_NUGET_CACHE" />\
  </config>\
</configuration>' > $PKG_NUGET_CONFIG/NuGet.Config


#################################################
# 配置用户环境
#################################################
# 切换用户和工作目录 - 准备开发环境
USER $USERNAME  # 切换到开发用户
WORKDIR $DEV_PROJECT  # 切换到项目目录


#################################################
# Avalonia 模板管理
#################################################
# 安装 Avalonia 模板 - 配置项目模板
RUN mkdir -p $USER_DOTNET_AVALONIA_TEMPLATES \
    # 安装最新的Avalonia模板
    && dotnet new install Avalonia.Templates::$AVALONIA_TEMPLATE_VERSION \
    # 确保模板目录权限正确
    && chown -R $USERNAME:$USERNAME $USER_DOTNET_AVALONIA_TEMPLATES


#################################################
# 预热 dotnet - 初始化开发环境
#################################################
# 运行一次dotnet命令，确保环境正确配置
RUN dotnet help


#################################################
# 设置默认命令 - 容器启动时的默认行为
#################################################
# 设置默认命令 - 容器启动时的默认行为
# 启动bash shell，提供交互式开发环境(生产环境需要修改这里)
CMD ["/bin/bash"]